package com.dd.keel.core.common.ini;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dd.keel.core.common.utils.FileUtil;
import com.dd.keel.core.common.utils.URLUtil;

/**
 * INI Dom , like XML Dom
 * @author Kevin.XU
 *
 */
public class IniDom {
	
	final static private Logger LOGGER = LoggerFactory.getLogger(IniDom.class);
	
	protected Vector<String> sectionSequence = new Vector<String>();
	protected Map<String, TreeMap<String,String> > sections = new HashMap<String, TreeMap<String,String> >();
	
	private transient String currentSecion;
	private transient TreeMap<String,String> current;
	private String charsetName = "UTF-8";
	
	private boolean useSplash = false;
	
	public IniDom(){}
	
	public IniDom(String charsetName)
	{
		this.charsetName = charsetName;
	}
	
	public boolean isUseSplash() {
		return useSplash;
	}

	public void setUseSplash(boolean useSplash) {
		this.useSplash = useSplash;
	}

	/***************************************************************
	 * 	
	 * Operations for reading
	 * 
	 **************************************************************/
	
	private void read(BufferedReader reader) throws IOException 
	{
		clear();
		String line;
		while ((line = reader.readLine()) != null) {
			parseLine(line);
		}
	}
	
	private String stripNote(String value)
	{
		//remove ';' note
		int notePos = value.lastIndexOf(';');
		if(notePos!=-1)
		{
			value = value.substring(0,notePos);
			value = value.trim();
		}
		//remove '#' note
		notePos = value.lastIndexOf('#');
		if(notePos!=-1)
		{
			value = value.substring(0,notePos);
			value = value.trim();
		}
		return value;
	}
	
    private String stipNoteWithSplash(String value,char noteMark)
    {
		StringBuilder sb = new StringBuilder();
		int notePos = value.indexOf(noteMark);
		if(notePos!=-1)
		{
			int beginPos = 0;
			boolean append = false;
			while(notePos!=-1)
			{
				if(notePos-1>=0 && value.charAt(notePos-1)=='\\')
				{
					sb.append( value.substring(beginPos,notePos-1) ).append(noteMark);
					beginPos = notePos + 1;
				}
				else
				{
					sb.append( value.substring(beginPos,notePos) );
					break;
				}
				notePos = value.indexOf(noteMark,beginPos);
				if(notePos==-1 && beginPos<=value.length()-1)
				{
					append = true;
				}
			}
			if(append)
			{
				sb.append( value.substring(beginPos) );
			}
			return sb.toString().trim();
		}
		else
		{
			return value.trim();
		}
    }
    
	private String stripNoteWithSplash(String value)
	{
		//remove ';' note
		value = stipNoteWithSplash(value,';');
		//remove '#' note
		value = stipNoteWithSplash(value,'#');
		return value;
	}
	
	private void parseLine(String line) 
	{
		line = line.trim();
		if (line.matches("\\[.*\\]")) {
			currentSecion = line.replaceFirst("\\[(.*)\\]", "$1");
			current = insertNewSection(null,currentSecion);
		} else if (line.matches(".*=.*")) {
			line = line.trim();
			if(line.startsWith("#") || line.startsWith(";"))
			{
				//skip this line note
			}
			else
			{
				if (current != null) {
					int i = line.indexOf('=');
					String name = line.substring(0, i);
					String value = line.substring(i + 1);
					if(name!=null) name=name.trim();
					if(value!=null)
					{
						value=value.trim();
						if(useSplash)
						{
							try{
								value = stripNoteWithSplash(value);
							}catch(Exception ex){
								LOGGER.error(ex.getMessage(), ex);
							}
						}
						else
						{
							try{
								value = stripNote(value);
							}catch(Exception ex){
								LOGGER.error(ex.getMessage(), ex);
							}
						}
					}
					if(name!=null && value!=null && !name.equals(""))
					{
						current.put(name, value);
					}
				}
			}
		}
	}
	
	public String getValue(String section, String name) 
	{
		if(section==null || name==null) return null;
		TreeMap<String,String> p = sections.get(section);
		if (p == null) {
			return null;
		}
		String value = p.get(name);
		return value;
	}
	
	public TreeMap<String,String> getSection(String section) 
	{
		if(section==null) return null;
		return sections.get(section);
	}
	
	public Iterator<String> allSectionNames() 
	{
		return sectionSequence.iterator();
	}
	
	public void clear()
	{
		sectionSequence.clear();
		sections.clear();
		currentSecion = null;
		current = null;
	}
	
	public void parseFile(File file) throws Exception 
	{
		BufferedReader reader = new BufferedReader(new FileReader(file));
		read(reader);
		reader.close();
	}
	
	public void parseFile(String fileName) throws Exception 
	{
		File file = new File(fileName);
		BufferedReader reader = null;
		if(file.exists())
		{
			reader = new BufferedReader(new FileReader(file));
		}
		else
		{
			reader = new BufferedReader(new InputStreamReader(URLUtil.getURLFromClasspath(fileName).openStream(),charsetName));
		}
		read(reader);
		reader.close();
	}
	
	public void parseFile(String fileName,Class contextClass) throws Exception 
	{
		File file = new File(fileName);
		BufferedReader reader = null;
		if(file.exists())
		{
			reader = new BufferedReader(new FileReader(file));
		}
		else
		{
			reader = new BufferedReader(new InputStreamReader(URLUtil.getURLFromClasspath(fileName,contextClass).openStream(),charsetName));
		}
		read(reader);
		reader.close();
	}
	
	public void parse(String iniContent) throws Exception 
	{
		BufferedReader reader = new BufferedReader(new StringReader(iniContent));
		read(reader);
		reader.close();
	}
	
	protected int indexOf(String sectionName)
	{
		int index = sectionSequence.indexOf(sectionName);
		return index;
	}
	
	/***************************************************************
	 * 	
	 * Operations for writing
	 * 
	 **************************************************************/
	
	protected TreeMap<String,String> newSectionMap()
	{
		return new TreeMap<String,String>(new StringComparator());
	}
	
	protected TreeMap<String,String> insertNewSection(String nearbySectionName,String newSectionName)
	{
		if(nearbySectionName==null)
		{
			TreeMap<String,String> p = newSectionMap();
			sectionSequence.add(newSectionName);
			sections.put(newSectionName, p);
			return p;
		}
		int index = indexOf(nearbySectionName);
		if(index==-1)
		{
			//not found 
			TreeMap<String,String> p = newSectionMap();
			sectionSequence.add(newSectionName);
			sections.put(newSectionName, p);
			return p;
		}
		else
		{
			TreeMap<String,String> p = newSectionMap();
			if(index==sectionSequence.size()-1)
			{
				sectionSequence.add(newSectionName);
			}
			else
			{
				sectionSequence.add(index+1, newSectionName);
			}
			sections.put(newSectionName, p);
			return p;
		}
	}
	
	public void setValue(String section, String name, String value) 
	{
		if(section==null || name==null || value==null) return;
		TreeMap<String,String> p = sections.get(section);
		if (p == null)
		{
			p = insertNewSection(null,section);
			sections.put(section, p);
		}
		if(name!=null) name=name.trim();
		if(value!=null) value=value.trim();
		if(name!=null && value!=null && !name.equals(""))
		{
			p.put(name, value);
		}
	}
	
	public void removeValue(String section, String name) 
	{
		if(section==null || name==null) return;
		TreeMap<String,String> p = sections.get(section);
		if (p != null) {
			if(name!=null)
			{
				name=name.trim();
				if(p.containsKey(name))
				{
					p.remove(name);
				}
			}
		}
	}
	
	public void setSection(String section, TreeMap<String,String> prop) 
	{
		if(section==null || prop==null) return;
		TreeMap<String,String> p = sections.get(section);
		if (p == null) 
		{
			if(prop!=null && !prop.isEmpty())
			{
				sections.put(section, prop);
			}
		}
		else
		{
			if(prop!=null && !prop.isEmpty())
			{
				p.putAll(prop);
				sections.put(section, p);
			}
		}
	}
	
	public TreeMap<String,String> addSection(String section,String nearbySectionName) 
	{
		if(section==null) return null;
		if (!sections.containsKey(section)) 
		{
			TreeMap<String,String> prop = insertNewSection(nearbySectionName,section);
			sections.put(section, prop);
			return prop;
		}
		else
		{
			return sections.get(section);
		}
	}
	
	public void renameSection(String section,String newSectionName)
	{
		if(section==null || newSectionName==null) return;
		int index = sectionSequence.indexOf(section);
		if(index!=-1)
		{
			sectionSequence.set(index, newSectionName);
			TreeMap<String,String> prop = getSection(section);
			removeSection(section);
			sections.put(newSectionName, prop);
		}
	}
	
	public void removeSection(String section) 
	{
		if(sections.containsKey(section))
		{
			sections.remove(section);
			sectionSequence.remove(section);
		}
	}
	
	public String asText()
	{
		StringBuilder sb = new StringBuilder();
		for(int i=0; i<sectionSequence.size(); ++i)
		{
			String sectionName = sectionSequence.get(i);
			TreeMap<String,String> props = sections.get(sectionName);
			sb.append('[').append(sectionName).append(']').append('\n');
			if(props!=null && !props.isEmpty())
			{
				Iterator keyIter = props.keySet().iterator();
				while(keyIter.hasNext())
				{
					String key = (String)keyIter.next();
					String value = props.get(key);
					sb.append(key).append('=').append(value).append('\n');
				}
			}
			sb.append('\n').append('\n');
		}
		return sb.toString();
	}
	
	public void output(String outputFileName) throws Exception
	{
		FileUtil.makeSureDirectory(outputFileName);
		File outputFile = new File(outputFileName);
		FileOutputStream fos = new FileOutputStream(outputFile);
		byte[] content = asText().getBytes(charsetName);
		fos.write(content);
		fos.close();
	}	
	
}
